{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, clone the Yana's **manopth** and Omid's **MANO** into `./thirdparty`\n",
    "\n",
    "manopth: https://github.com/hassony2/manopth, SHA `4f1dcad1201ff1bfca6e065a85f0e3456e1aa32b`  \n",
    "MANO: https://github.com/otaheri/MANO,  SHA `5869ab059c1bf31cc724f57eaf93e041135e8960`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os, sys\n",
    "\n",
    "module_path = os.path.abspath(os.path.join('..'))\n",
    "if module_path not in sys.path:\n",
    "    sys.path.append(module_path)\n",
    "\n",
    "import torch\n",
    "import numpy as np\n",
    "import thirdparty.manopth.manopth.manolayer as YanaMano\n",
    "import thirdparty.MANO.mano as OmidMano\n",
    "import manotorch.manolayer as ThisMano\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### test PCA mode"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_size = 10\n",
    "ncomps = 15\n",
    "\n",
    "seed = 23\n",
    "np.random.seed(seed)\n",
    "torch.manual_seed(seed)\n",
    "\n",
    "random_shape = torch.rand(batch_size, 10)\n",
    "# Generate random pose parameters, including 3 values for global axis-angle rotation\n",
    "random_pose = torch.rand(batch_size, 3 + ncomps)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/lixin/Doc/manotorch/thirdparty/manopth/manopth/manolayer.py:72: UserWarning: The given NumPy array is not writable, and PyTorch does not support non-writable tensors. This means writing to this tensor will result in undefined behavior. You may want to copy the array to protect its data or make it writable before converting it to a tensor. This type of warning will be suppressed for the rest of this program. (Triggered internally at /opt/conda/conda-bld/pytorch_1678402312629/work/torch/csrc/utils/tensor_numpy.cpp:206.)\n",
      "  torch.Tensor(smpl_data['betas'].r).unsqueeze(0))\n"
     ]
    }
   ],
   "source": [
    "yana_mano_layer = YanaMano.ManoLayer(\n",
    "    center_idx=None,\n",
    "    side=\"right\",\n",
    "    mano_root=\"../assets/mano/models/\",\n",
    "    use_pca=True,\n",
    "    flat_hand_mean=True,\n",
    "    ncomps=ncomps\n",
    ")\n",
    "\n",
    "# hand's vertices and joints. The unit set by Yana is millimeters.\n",
    "V1, J1 = yana_mano_layer(random_pose, random_shape)\n",
    "# change to meter, \n",
    "V1 = V1 / 1000.0  \n",
    "J1 = J1 / 1000.0\n",
    "\n",
    "this_mano_layer = ThisMano.ManoLayer(\n",
    "    rot_mode=\"axisang\",\n",
    "    center_idx=None,\n",
    "    side=\"right\",\n",
    "    mano_assets_root=\"../assets/mano\",\n",
    "    use_pca=True,\n",
    "    flat_hand_mean=True,\n",
    "    ncomps=ncomps\n",
    ")\n",
    "\n",
    "out = this_mano_layer(random_pose, random_shape)\n",
    "V2, J2 = out.verts, out.joints\n",
    "\n",
    "assert torch.allclose(V1, V2, atol=1e-6) # less than 0.001 mm is acceptable\n",
    "assert torch.allclose(J1, J2, atol=1e-6)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "omid_mano_model = OmidMano.load(model_path=\"../assets/mano/models\",\n",
    "                     is_rhand= True,\n",
    "                     num_pca_comps=ncomps,\n",
    "                     batch_size=batch_size,\n",
    "                     flat_hand_mean=True)\n",
    "\n",
    "output = omid_mano_model(\n",
    "    global_orient=random_pose[:,:3],\n",
    "    hand_pose=random_pose[:,3:],\n",
    "    transl = None,\n",
    "    betas=random_shape, \n",
    "    return_verts=True,\n",
    "    return_tips = True\n",
    ")\n",
    "\n",
    "V3, J3 = output.vertices, output.joints\n",
    "assert torch.allclose(V2, V3, atol=1e-6) # less than 0.001 mm is acceptable"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In Omid's MANO model, the definitions of Joint order and Joint tips index differ from those of Yana's and ours.  \n",
    "However, converting between them is quite simple.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "OURS_TIP_IDS = {\n",
    "    'thumb':\t\t745,\n",
    "    'index':\t\t317,\n",
    "    'middle':\t\t444,\n",
    "    'ring':\t\t    556,\n",
    "    'pinky':\t\t673,\n",
    "}\n",
    "REORDER_IDX = [0, 13, 14, 15, 16, 1, 2, 3, 17, 4, 5, 6, 18, 10, 11, 12, 19, 7, 8, 9, 20]\n",
    "\n",
    "def add_tips(vertices, joints, joint_ids = None):\n",
    "    if joint_ids is None:\n",
    "        joint_ids = torch.tensor(list(OURS_TIP_IDS.values()),\n",
    "                                dtype=torch.long)\n",
    "    extra_joints = torch.index_select(vertices, 1, joint_ids)\n",
    "    joints = torch.cat([joints, extra_joints], dim=1)\n",
    "\n",
    "    return joints\n",
    "\n",
    "J3_ = J3[:, :16] # remove the tips in Omid's definition\n",
    "tips = V3[:, [745, 317, 444, 556, 673]] # get the tips from Yana's definition\n",
    "J3_ = torch.cat([J3_, tips], 1)\n",
    "new_J3 = J3_[:, REORDER_IDX] # reorder the joints to match Yana's definition\n",
    "\n",
    "assert torch.allclose(J2, new_J3, atol=1e-6)  # less than 0.001 mm is acceptable"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "manotorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.10"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
